#!/usr/bin/env bash
set -Eeuo pipefail

####################
# Global variables
####################
# extern $PROCESS
# extern $STORAGE
# extern $HOST
# extern $RUN_LOCAL
NET_OPTS=""
GATEWAY=""
IP=""
VM_NET_MAC=""
# specific network interface device in container OS
VM_NET_DEV=""


####################
# Static variables
####################
ADD_ERR="Please add the following setting to your container:"

USERMODE_PORTS="55555"
USERMODE_HOSTNAME="ohos"
USERMODE_IPADDR="20.20.20.21"

TAP_NAME_PREFIX="ohostap"
INSTANCE_TAP_NAME=""
BRIDGE_NAME="ohosbr0"
BRIDGE_IPADDR="192.168.111.1"

BRIDGE_PREFIX=$(echo $BRIDGE_IPADDR | cut -d '.' -f 1-3)
DNSMASQ_CONF="
interface=${BRIDGE_NAME}
# DHCP range (for VMs connected to bridge)
dhcp-range=${BRIDGE_PREFIX}.10,${BRIDGE_PREFIX}.100,24h
# Gateway for the VMs (use the bridge IP address)
dhcp-option=3,$BRIDGE_IPADDR
# DNS server for the VMs (use the bridge IP address)
dhcp-option=6,$BRIDGE_IPADDR
"


###################
# Helper fuctions
###################

# Param[0]: specific mac address, empty for random generation
_getNetworkInfo() {

  local INSTANCE_MAC=$1

  if [ -z "$VM_NET_DEV" ]; then
    # Give Kubernetes priority over the default interface
    [ -d "/sys/class/net/net0" ] && VM_NET_DEV="net0"
    [ -d "/sys/class/net/net1" ] && VM_NET_DEV="net1"
    [ -d "/sys/class/net/net2" ] && VM_NET_DEV="net2"
    [ -d "/sys/class/net/net3" ] && VM_NET_DEV="net3"
    # Automaticly detect the default network interface
    [ -z "$VM_NET_DEV" ] && VM_NET_DEV=$(awk '$2 == 00000000 { print $1 }' /proc/net/route)
    [ -z "$VM_NET_DEV" ] && VM_NET_DEV="eth0"
  fi

  if [ ! -d "/sys/class/net/$VM_NET_DEV" ]; then
    error "Network interface '$VM_NET_DEV' does not exist inside the container!"
    error "$ADD_ERR -e \"VM_NET_DEV=NAME\" to specify another interface name." && exit 27
  fi

  if [ -z "$INSTANCE_MAC" ]; then
    local file="$STORAGE/$PROCESS.mac"
    [ -s "$file" ] && INSTANCE_MAC=$(<"$file")
    if [ -z "$INSTANCE_MAC" ]; then
      # Generate MAC address based on Docker container ID in hostname
      INSTANCE_MAC=$(echo "$HOST`date "+%s"`" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')
      echo "${INSTANCE_MAC^^}" > "$file"
    fi
  fi

  VM_NET_MAC="${INSTANCE_MAC^^}"
  VM_NET_MAC="${VM_NET_MAC//-/:}"

  if [[ ${#VM_NET_MAC} == 12 ]]; then
    m="$VM_NET_MAC"
    VM_NET_MAC="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}"
  fi

  if [[ ${#VM_NET_MAC} != 17 ]]; then
    error "Invalid MAC address: '$VM_NET_MAC', should be 12 or 17 digits long!" && exit 28
  fi

  GATEWAY=$(ip route list dev "$VM_NET_DEV" | awk ' /^default/ {print $3}')
  IP=$(ip address show dev "$VM_NET_DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/)

  return 0
}

# Param[0]: use DHCP? ("Y" | "N")
_checkOS() {

  local USE_DHCP=$1

  local name
  local os=""
  local otype="macvlan"
  name=$(uname -a)

  [[ "${name,,}" == *"darwin"* ]] && os="Docker Desktop for macOS"
  [[ "${name,,}" == *"microsoft"* ]] && os="Docker Desktop for Windows"

  if [[ "$USE_DHCP" == [Yy1]* ]]; then
    otype="macvtap"
    [[ "${name,,}" == *"synology"* ]] && os="Synology Container Manager"
  fi

  if [ -n "$os" ]; then
    warn "you are using $os which does not support $otype, please revert to bridge networking!"
  fi

  return 0
}

# Build network bridge(s) and
# configure DNS server & DHCP server
initializeDNS() {

  # Reset network bridge (with a same name)
  BRIDGE_REC=$(brctl show | grep $BRIDGE_NAME || true)
  if [ -n "$BRIDGE_REC" ]; then
    # Try to remove previous bridge
    ip link set $BRIDGE_NAME down
    brctl delbr $BRIDGE_NAME
  fi

  # Build network bridge
  # ip link add name ohosbr0 type bridge
  if ! brctl addbr $BRIDGE_NAME; then
    error "Failed to create network bridge $BRIDGE_NAME" && return 1
  fi
  if ! ip addr add $BRIDGE_IPADDR/24 dev $BRIDGE_NAME; then
    error "Failed to assign IPv4 address ($BRIDGE_IPADDR) to bridge $BRIDGE_NAME" && return 1
  fi
  if ! ip link set $BRIDGE_NAME up; then
    error "Failed to activate bridge $BRIDGE_NAME" && return 1
  fi

  # if [ $RUN_LOCAL -eq 0 ]; then

  echo "$DNSMASQ_CONF" > /etc/dnsmasq.conf
  if ! /usr/sbin/dnsmasq; then
    error "Failed to start dnsmasq. Reason: $?" && return 1
  fi

  # else 
  # info "Skipped starting dnsmasq (local)"
  # fi

  return 0
}

# Configure NAT mode
_configureNAT() {

  local INSTANCE_IDX=$1
  local INSTANCE_MAC=$2
  INSTANCE_TAP_NAME="${TAP_NAME_PREFIX}${INSTANCE_IDX}"

  # Reset tap device with a same name
  TAP_REC=$(ip tuntap show | grep ${INSTANCE_TAP_NAME} || true)
  if [ -n "$TAP_REC" ]; then
    # Try to remove tap previous device
    ip link set ${INSTANCE_TAP_NAME} down &> null
    ip tuntap del dev ${INSTANCE_TAP_NAME} mode tap &> null
  fi

  # Create a tap device for current instance
  if ! ip tuntap add dev ${INSTANCE_TAP_NAME} mode tap; then
    error "Failed to create tap device for instance $INSTANCE_IDX" && return 1
  fi
  if ! ip link set ${INSTANCE_TAP_NAME} up; then
    ip tuntap del dev ${INSTANCE_TAP_NAME} mode tap &> null || true
    error "Failed to activate tap device ${INSTANCE_TAP_NAME}" && return 1
  fi
  if ! ip link set ${INSTANCE_TAP_NAME} master ${BRIDGE_NAME}; then
    ip link set ${INSTANCE_TAP_NAME} down &> null || true
    ip tuntap del dev ${INSTANCE_TAP_NAME} mode tap &> null || true
    error "Failed to associate tap ${INSTANCE_TAP_NAME} with ${BRIDGE_NAME}" && return 1
  fi

  NET_OPTS="-netdev tap,id=net${INSTANCE_IDX},ifname=${INSTANCE_TAP_NAME},script=no,downscript=no -device virtio-net-pci,netdev=net${INSTANCE_IDX},mac=${INSTANCE_MAC}"
  return 0
}

# Param[0]: mapped port list
_getUserPorts() {

  local args=""
  local list=$1
  local hdc="55555"
  local rdp="3389"

  [ -z "$list" ] && list="$hdc,$rdp" || list+=",$hdc,$rdp"

  list="${list//,/ }"
  list="${list## }"
  list="${list%% }"

  for port in $list; do
    args+="hostfwd=tcp::$port-$USERMODE_IPADDR:$port,"
  done

  echo "${args%?}"
  return 0
}

# Configure usermode networking
_configureUser() {
  NET_OPTS="-netdev user,id=hostnet0,host=${USERMODE_IPADDR%.*}.1,net=${USERMODE_IPADDR%.*}.0/24,dhcpstart=$USERMODE_IPADDR,hostname=$USERMODE_HOSTNAME"

  local forward
  forward=$(getUserPorts "$USERMODE_PORTS")
  [ -n "$forward" ] && NET_OPTS+=",$forward"

  return 0
}

# Param[0]: use network? ("user" | "NAT" | "N")
configNetwork() {

  local NETWORK=$1

  if [[ "$NETWORK" == [Nn]* ]]; then
    NET_OPTS=""
    return 0
  fi

  info "Initializing network..."

  _getNetworkInfo

  info "Host: $HOST  IP: $IP  Gateway: $GATEWAY  Interface: $VM_NET_DEV  MAC: $VM_NET_MAC"
  [ -f /etc/resolv.conf ] && grep '^nameserver*' /etc/resolv.conf
  echo

  if [[ "$IP" != "172."* ]] && [[ "$IP" != "10.8"* ]] && [[ "$IP" != "10.9"* ]]; then
    _checkOS
  fi

  if [[ "${NETWORK,,}" != "user"* ]]; then

    info "Select NAT mode"

    # Configure for tap interface
    if ! _configureNAT; then
      NETWORK="user"
      warn "falling back to usermode networking! Performance will be bad and port mapping will not work."
    fi
  fi

  if [[ "${NETWORK,,}" == "user"* ]]; then

    info "Select usermode networking"

    # Configure for usermode networking (slirp)
    configureUser || exit 24
  fi

  info "Initialized network successfully!"
  return 0
}


